home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1996 #14 / Monster Media No. 14 (April 1996) (Monster Media, Inc.).ISO / prog_d / oleauttr.zip / OLEAUTO.TXT < prev    next >
Text File  |  1996-01-04  |  24KB  |  556 lines

  1.  
  2.                                 OLEAUTO.TXT
  3.         Supplementary documentation for TOLEAutoClient component
  4.  
  5.         ************************************************************
  6.         For support, send E-mail to:
  7.                         sraike@iconz.co.nz
  8.         or CompuServe:  100236,1656
  9.         or send FAX to: 64-9-832-0088
  10.         ************************************************************
  11.  
  12.    This file describes how to write a "mirror" class for Delphi that
  13. lets your application access properties and methods of an OLE Automation
  14. server just as if they were properties and methods of one of your
  15. own objects.
  16.  
  17.    Since TOleAutoClient was first released in April 1995, filling
  18. the gap left by Delphi's lack of OLE Automation support, customers
  19. have used it to control a wide variety of OLE Automation servers
  20. including Microsoft Office products, ABC Flowcharter, Visio, Acrobat,
  21. Watermark and a number of others.  (All these names are the property
  22. of their respective trademark owners.)
  23.  
  24.    One description here uses Microsoft Word for Windows 6.0 as a typical
  25. OLE Automation server.  "Word", "Excel", "Word for Windows", "Windows"
  26. and "Visual Basic" are registered trademarks of Microsoft Corporation.
  27. For more information, refer to the README.1ST and OLEAUTO.INT files
  28. and to the accompanying compressed example files like WORDDRIV.ZIP,
  29. NUMSVR.ZIP and VISIOOLE.ZIP that contain working sample code.
  30.  
  31.    Many servers "expose" a hierarchy of nested OLE objects and classes.
  32. After reading the introductory material in this file, you should refer
  33. to the file NESTOBJ.TXT for a discussion of how to write mirror classes
  34. that describe such nested objects.  You should also refer to the
  35. WHATSNEW.TXT file for a description of the features incorporated
  36. in the latest release of TOLEAutoClient.
  37.  
  38. ******************************************
  39. OVERVIEW
  40. ******************************************
  41.  
  42.    Some Windows development tools (Microsoft Visual Basic, for
  43. one) let you control OLE Automation servers (like Microsoft Word 6.0,
  44. for example).  An OLE Automation server "exposes" certain properties
  45. and methods for access by other programs.  Microsoft Word 6.0 exposes
  46. most of its WordBasic macro language; other servers expose properties,
  47. objects and methods as detailed in their documentation.  For Word 6.0,
  48. you should refer to Microsoft documentation such as its online Help
  49. and the Microsoft Office Developers' Kit (ODK).
  50.  
  51.    For example, among the WordBasic methods Word exposes are:
  52.  
  53.             FileNew                - tells Word to open a new file
  54.             ToolsOptionsEdit       - sets Tools|Edit|Options dialog items
  55.             ToolsMacro             - tells Word to run a template macro
  56.             FileClose              - tells Word to close a current file
  57.  
  58. ******************************************
  59. EXAMPLE 1 - (WORDDRIV.ZIP)
  60. ******************************************
  61.  
  62.    Suppose you want your program to tell Word to open a new file based
  63. on the NORMAL.DOT template, set and clear some check boxes on
  64. the Edit tab of the Tools|Options dialog, run a WordBasic macro
  65. called DummyMsg that is stored in the NORMAL.DOT template, and then
  66. close the file without saving it.  Referring to the Word documentation,
  67. you discover that the WordBasic methods you need to call, with their
  68. parameters, are:
  69.  
  70.            FileNew "NORMAL.DOT"
  71.            ToolsOptionsEdit 1,1,0,0,0,0,0,0,0
  72.            ToolsMacro "DummyMsg", 1
  73.            FileClose 2
  74.  
  75.    (WordBasic actually allows you to use named arguments like .Run = 1
  76.     instead of "positional" arguments; neither TOLEAutoClient
  77.     nor Visual Basic yet supports named arguments.  Also, be aware that
  78.     WordBasic online Help sometimes fails to list the named arguments
  79.     in their correct positional order; this information is given in
  80.     the file POSITION.TXT in the Office Developers' Kit.  Alternatively,
  81.     you can record a WordBasic macro and then look at the macro code it
  82.     generates for the correct parameter order.)
  83.  
  84.    If you were writing Visual Basic code to do this, it would look like:
  85.  
  86.         Dim ob As Object
  87.         Set ob = CreateObject("word.basic")
  88.         ob.FileNew "NORMAL.DOT"
  89.         ob.ToolsOptionsEdit 1,1,0,0,0,0,0,"Microsoft Word"
  90.         ob.ToolsMacro "DummyMsg", 1
  91.         ob.FileClose 2
  92.         Set ob = Nothing
  93.              ...
  94.  
  95.    Using the TOleAutomationClient component, you'll write the following
  96. code in Delphi to accomplish the same thing:
  97.  
  98.         var
  99.            WordOb : TWordObj;
  100.         begin
  101.            WordOb := TWordObj.CreateObject('word.basic');
  102.            WordOb.FileNew('NORMAL.DOT');
  103.            WordOb.ToolsOptionsEdit; { The example has hard-coded options...}
  104.            WordOb.ToolsMacro('DummyMsg'); { Ditto for the second argument...}
  105.            WordOb.FileClose(2);
  106.            WordOb.Release;
  107.                 ...
  108.  
  109.    But there's a catch!  (Read on -- it has to do with the TWordObj class.)
  110.  
  111.    Since Delphi is a compiled language, before you can call a method
  112. or refer to a property for an object you need to let Delphi know that
  113. the object belongs to a class that contains that method or property.
  114. The way to do that in Delphi is to create a new unit and in it to declare
  115. a new class with the needed methods and properties.  Fortunately, the
  116. TOleAutoClient component simplifies the process by letting you
  117. derive the class from a parent class it supplies: TOleObject.  In the case
  118. of the example given above, the interface section of the unit looks like:
  119.  
  120.         unit WordCls;
  121.  
  122.         interface
  123.  
  124.         uses
  125.            SysUtils, OleAuto;  { OLEAUTO.DCU must be in \DELPHI\LIB }
  126.  
  127.         type
  128.            TWordObj = class(TOleObject) {TOleObject is defined in OLEAUTO.DCU}
  129.            public
  130.                 procedure FileNew(TemplateName : String);
  131.                 procedure ToolsMacro(MacroName : String);
  132.                 procedure FileClose(CloseMode : Integer);
  133.                 procedure ToolsOptionsEdit; {arguments omitted for simplicity}
  134.            end;
  135.  
  136.    So that these methods can communicate with Word through OLE Automation,
  137. in the implementation section of the WordCls unit you define the class
  138. methods you just declared, inserting calls to methods in the parent
  139. TOleObject class that pass each of the method arguments and define their
  140. types, and that perform the actual OLE method calls.  Refer to the file
  141. OLEAUTO.INT for a description of the allowable types.
  142.  
  143.    In the implementation of a method call, you need to do two things:
  144.         1. Pass each argument and define its type.  To do this, you
  145.            call SetOleMethodArg (a member of the parent TOLEObject class)
  146.            once for each argument to be passed.  The order in which
  147.            the arguments are passed is FROM RIGHT TO LEFT!
  148.         2. Have OLE call the method in the server application.  To do this,
  149.            you call either CallOleFunction or CallOleProc, according to
  150.            whether or not the server method returns a value.
  151.  
  152.    In this example, none of the methods returns a value; in the next ones
  153. you'll see how to call a method that does return a value, and how to
  154. access properties (in addition to methods) exposed by a server.
  155.  
  156.    The implementation section looks like this:
  157.  
  158. implementation
  159.  
  160. procedure TWordObj.ToolsOptionsEdit;
  161. const
  162.       pszMSW : PChar = 'Microsoft Word';
  163. var
  164.       x : Integer;
  165. begin
  166.     { It's necessary to supply ALL the arguments to ToolsOptionsEdit. }
  167.     {  We always set the arguments in reverse order: i.e., right to left. }
  168.     {
  169.         For this WordBasic command, all the options indicate check-box status
  170.         values (0 = unchecked) in the Word 6 Tools|Options\Edit dialog.
  171.     }
  172.     SetOleMethodArg('PChar', pszMSW); { .PictureEditor }
  173.  
  174.     { The following are just some sample option settings. }
  175.     x := 0; { Unchecked. }
  176.     SetOleMethodArg('Integer', x);  { .AllowAccentedUppercase }
  177.     SetOleMethodArg('Integer', x);  { .SmartCutPaste }
  178.     SetOleMethodArg('Integer', x);  { .Overtype }
  179.     SetOleMethodArg('Integer', x);  { .InsForPaste }
  180.     SetOleMethodArg('Integer', x);  { .AutoWordSelection }
  181.  
  182.     x := 1; { Checked. }
  183.     SetOleMethodArg('Integer', x);  { .DragAndDrop }
  184.     SetOleMethodArg('Integer', x);  { .ReplaceSelection }
  185.  
  186.     {After setting the arguments, call the method.  This method
  187.      doesn't return a value, so we use CallOleProc; if it did return
  188.      a value, we'd use CallOleFunction.}
  189.     CallOleProc('ToolsOptionsEdit');
  190. end;
  191.  
  192. procedure TWordObj.FileNew(TemplateName : String);
  193. var
  194.     pszName : PChar; 
  195. begin
  196.     pszName := StrAlloc(256); { Get some string space. }
  197.     StrPLCopy(pszName, TemplateName, 40);
  198.  
  199.     { Tell Word the name of the template. }
  200.     SetOleMethodArg('PChar', pszName);
  201.  
  202.     CallOleProc('FileNew');
  203.  
  204.     StrDispose(pszName);
  205. end;
  206.  
  207. procedure TWordObj.ToolsMacro(MacroName : String);
  208. var
  209.     mode : Integer;
  210.     pszName : PChar;
  211. begin
  212.     pszName := StrAlloc(256); { Get some string space. }
  213.     mode := 1;
  214.     { Tell Word to .Run the macro. }
  215.     { You might want to write a sample Word
  216.       macro and save it in the NORMAL.DOT template, perhaps something like:
  217.  
  218.         Sub Main
  219.         MsgBox "Clicked me!"
  220.         End Sub
  221.     }
  222.  
  223.     { Set the arguments from right to left.}
  224.     SetOleMethodArg('Integer', mode); { This arg *MUST* be present, even if its type is '' }
  225.     StrPLCopy(pszName, MacroName, 40);
  226.     SetOleMethodArg('PChar', pszName); { WordBasic macro name }
  227.  
  228.     { Execute it. }
  229.     CallOleProc('ToolsMacro');
  230.  
  231.     StrDispose(pszName);
  232. end;
  233.  
  234. procedure TWordObj.FileClose(CloseMode : Integer);
  235. begin
  236.     SetOleMethodArg('Integer', CloseMode);
  237.     CallOleProc('FileClose');
  238. end;
  239.  
  240. end.
  241.  
  242. ******************************************
  243. IN-PROCESS SERVERS AND LOCAL SERVERS
  244. ******************************************
  245.  
  246. See the file WHATSNEW.TXT for a discussion of the support provided
  247. for controlling in-process OLE Automation servers.
  248.  
  249. ******************************************
  250. CONTROLLING "EMBEDDED" OLE OBJECTS AND
  251. ALREADY-RUNNING INSTANCES
  252. ******************************************
  253.  
  254. See the file WHATSNEW.TXT for a discussion of the support provided
  255. for this topic.
  256.  
  257. ******************************************
  258. MORE INFORMATION ON ARGUMENT TYPES
  259. ******************************************
  260.  
  261.    Internally, all parameters to OLE methods are actually passed as
  262. VARIANT types, directly analogous to the Variant type in Microsoft
  263. Visual Basic.  This type is declared within the OLEAUTO.DCU unit,
  264. along with a number of OLE DLL routines for manipulating VARIANT
  265. quantities.  However, when you pass arguments using SetOleMethodArg,
  266. and when you call OLE server methods that return a value, or when you
  267. get and set properties of OLE objects, you need to pass a string
  268. denoting the type of the argument or return value.  See the file
  269. OLEAUTO.INT for a list of the allowable Delphi types.
  270.  
  271.    You can pass arguments to methods both by value and by reference; a
  272. reference argument is equivalent to one declared as var in Pascal.
  273. In order to pass an argument by reference, you prefix the name of its
  274. type (the string passed as the first parameter to SetOleMethodArg) with
  275. the symbol "&".  Most servers will expect their arguments to be passed
  276. by value, but there are cases when they will accept arguments passed
  277. by reference.  For example, an OLE (server) method may be designed to
  278. modify the values of one or more of its arguments.
  279.  
  280.    In order to pass an argument defined as a user-defined type, OLE
  281. requires you to pass a pointer to the argument instead.  In this case,
  282. you should cast the pointer to a generic pointer (i.e., Pointer(myptr))
  283. before passing it with SetOleMethodArg as an argument of type 'Pointer'.
  284.  
  285.    The _only_ type names allowed for the first parameter to the
  286. SetOleMethodArg method are those listed in OLEAUTO.INT.  The same
  287. is true for the allowable return types of methods returning values
  288. (you specify the return type in the second parameter to
  289. CallOleFunction) and for retrieving and setting properties
  290. with the GetOleProperty and SetOleProperty methods.
  291.  
  292.    Many servers define and "expose" a hierarchy of "nested" objects,
  293. collections, etc.  Examples are Excel, Visio (TM), etc.  To access
  294. these nested objects, often the servers provide methods or properties
  295. whose type is a kind of object pointer.  In some cases, the server
  296. documentation describes the type returned by such methods/properties
  297. as "LPDISPATCH", or "PDISP", or a similar type.  With the TOleAutoClient
  298. component, the type name you use for this purpose is 'pInterface'; it
  299. means essentially the same thing as LPDISPATCH, which is one of the
  300. type names often used in C/C++ language documentation of OLE automation.
  301.  
  302. ******************************************
  303. EXAMPLE 2 - (NUMSVR.ZIP)
  304. ******************************************
  305.  
  306.    As another example, let's look at a small OLE Automation server
  307. called NUMSVR1.EXE, distributed along with this component.  This server
  308. simulates an integer property by exposing read and write methods GetX
  309. and SetX, and one method, named Incr, that accepts a single integer
  310. parameter PASSED BY REFERENCE.  The Incr() method's only effect is to
  311. increment its argument.
  312.  
  313.    In the NumSvr example program, you can watch how your Delphi program
  314. passes an integer variable to the server which ends up being incremented
  315. as a result of the call to Incr().  This is a useful feature not (yet)
  316. supported by Visual Basic.
  317.  
  318.    The "mirror" class TNumSvrOb is contained in the NUMSVCLS.PAS unit;
  319. its interface section exactly reflects the OLE server's exposed
  320. methods, and the implementation is straightforward.  Notice how
  321. the argument to the Incr method is passed via SetOleMethodArg with a
  322. type name of '&integer' to indicate that the argument is being passed
  323. by reference rather than by value.  Also notice how the
  324. function GetX is implemented.  Here the call to CallOleFunction
  325. specifies the name of the OLE method ('GetX'), the return type ('integer')
  326. and the name of the variable into which the return value is to be put.
  327.  
  328.  
  329. unit Numsvcls;
  330.  
  331. interface
  332.  
  333. uses
  334.    SysUtils, OleAuto;
  335.  
  336. type
  337.     TNumSvrOb = class(TOleObject)
  338.     private
  339.         procedure SetX(newx : Integer);
  340.         function GetX : Integer;
  341.     public
  342.         property X : integer read GetX write SetX;
  343.         procedure Incr(var x : Integer);
  344.     end;
  345.  
  346. implementation
  347.  
  348. procedure TNumSvrOb.Incr(var x : Integer);
  349. begin
  350.     { Specify argument type as '&Integer' to pass by reference. }
  351.     SetOleMethodArg('&Integer', x);
  352.     CallOleProc('Incr');
  353. end;
  354.  
  355. procedure TNumSvrOb.SetX(newx : Integer);
  356. begin
  357.     SetOleMethodArg('integer', newx);
  358.     CallOleProc('SetX');
  359. end;
  360.  
  361. function TNumSvrOb.GetX : Integer;
  362. var
  363.     x : Integer;
  364. begin
  365.     CallOleFunction('GetX', 'Integer', x);
  366.     Result := x;
  367. end;
  368.  
  369. end.
  370.  
  371. ******************************************
  372. ERROR CHECKING AND HANDLING
  373. ******************************************
  374.  
  375.    ++++++++++++++++++++++++++++++
  376.    Inability to start up a server
  377.    ++++++++++++++++++++++++++++++
  378.  
  379.    The CreateObject and CreateInProcessObject constructors
  380. for your mirror class, inherited from TOleObject, will raise
  381. an EOleAutoNoCreate exception if an OLE server cannot be started up
  382. successfully.  This can happen in the following situations:
  383.  
  384.         a)      The ProgID string that identifies the server object can't
  385.         be found in your system registry (REG.DAT under Windows 3.x).
  386.         You can inspect the registry by running REGEDIT /V from
  387.         Program Manager.  This usually means that the server has
  388.         never registered itself, but may mean that the registry
  389.         has become corrupted.  With many OLE Automation servers,
  390.         you need to run the server application under Windows at
  391.         least once as a stand-alone application so that it can
  392.         register itself.  Alternatively, some servers come with
  393.         setup or installation programs that accomplish this task.
  394.  
  395.  
  396.         b)      Windows can't locate the executable file or DLL
  397.         containing the server in the place it expects to find it.
  398.         This may mean that the server application has been moved
  399.         or re-installed into a different directory than the one
  400.         from which it was originally installed or registered.
  401.         Re-installing the server application will correct
  402.         this problem.
  403.  
  404.         c)      Either Windows itself or one of its OLE support DLLs
  405.         encountered an error trying to start up the server application.
  406.         This can occur as a result of a bug in the server itself, or
  407.         may be due to a system failure, Windows GPF, etc. during the
  408.         debugging process.  It is also possible that the versions
  409.         of the Windows OLE system DLLs (normally installed by Windows
  410.         in \WINDOWS\SYSTEM) are obsolete; in particular, the versions
  411.         of COMPOBJ.DLL, OLE2DISP.DLL and OLE2.DLL should be no
  412.         earlier than version 2.02.
  413.  
  414.         d)      Programmers sometimes forget to place a TOleAutoClient
  415.         component on a form whose unit uses the component.  The
  416.         application will compile correctly, but since Delphi will never
  417.         call the TOLEAutoClient constructor, the Windows OLE libraries
  418.         will never be correctly initialised.  Mysteriously, although
  419.         this bug can happen under Windows 3.x, the application will
  420.         run successfully under Windows 95!
  421.  
  422.    You can handle the EOleAutoNoCreate exception yourself with a try block
  423. or let Delphi handle it for you.
  424.  
  425.    Refer to the file WHATSNEW.TXT for additional information
  426. regarding exceptions raised by the GetObject mirror class constructor
  427. used to "hook into" and control a running instance of an OLE Automation
  428. server.
  429.  
  430.    If there is insufficient memory to complete a SetOleMethodArg call,
  431. typically with a string argument, the TOleAutomationClient component will
  432. raise an EOleAutoOutOfMemory exception.  In the unlikely event this occurs,
  433. you can handle the exception yourself or let Delphi handle it.
  434.  
  435.    +++++++++++++++++++++++++++++++++++++++
  436.    OLE method calls don't execute properly
  437.    +++++++++++++++++++++++++++++++++++++++
  438.  
  439.    Attempts to retrieve or set a server's properties or to call its methods
  440. will not generate exceptions if they fail.  The most common situation in
  441. which this will occur is when there is a type mismatch between the mirror
  442. class and the server, either for a property or a method argument or return
  443. type.  Some servers will attempt to coerce an argument to the correct type,
  444. but not all.  Also, some server methods may be incorrectly documented as
  445. functions returning a type of VT_EMPTY (corresponding to a Delphi type
  446. string of '', an empty string) when they really should be implemented as
  447. procedures with no return value.  Finally, some methods may allow "optional"
  448. arguments to be omitted, while others may require all specified arguments
  449. to be present and will cause en error if the number of arguments is
  450. incorrect.  Check your server documentation carefully (if there is any).
  451.  
  452.    The only argument/method/property types supported by TOLEAutoClient are
  453. those documented in the OLEAUTO.INT file.  Note also that the PInterface
  454. type referred to in this documentation may be referred to as "pIDisp",
  455. "pDisp", "pIDispatch" or "LPDISPATCH" in some server documentation.
  456. Your mirror class should be written to pass 'pInterface' as a type name
  457. in such cases.
  458.  
  459.    You can check for success or failure of a call to any of CallOleProc,
  460. CallOleFunction, GetOleProperty or SetOleProperty by a call to the
  461. GetOleMethodFailFlag method, declared in the TOleObject class as
  462.         function TOleObject.GetOleAutoFailFlag : Boolean;
  463. This returns an internal flag which will be True if the call failed (i.e.,
  464. if the Windows OLE DLLs returned with an error indication) or False if
  465. the call was executed successfully.  An alternative and equivalent
  466. way to check this status is to refer to the OleAutoFailFlag property
  467. of your mirror class object descended from TOleObject.
  468.  
  469.    For more detailed information regarding the nature of an error
  470. generated during one of the above calls, you can access the
  471. OleAutoErrCode property (of type HRESULT) of your mirror class
  472. object descended from TOleObject.  If there has been no error,
  473. this property should have the value S_OK.  Refer to the OLEAUTO.INT
  474. file for details of the error codes returned through this property.
  475.  
  476.    Alternatively, if you prefer to use Delphi's exception-handling
  477. mechanism to signal failure of OLE method calls or property accesses,
  478. the code given below makes this easy to accomplish.  You simply
  479. derive an intermediate class, say TOleObjectEx, from TOleObject
  480. and use this intermediate class instead of TOleObject as the
  481. ancestor from which you derive your mirror class. Working code
  482. for this class appears below.  You can place it in a new unit
  483. called OLEAUTX.PAS that you add to your project.  Be sure to
  484. replace OLEAUTO with OLEAUTX in the Uses clause of your mirror
  485. class unit so your mirror class can "see" TOleObjectEx.  Note
  486. that the code below displays exception messages that don't
  487. depend on the specific value of OleAutoErrCode.  Feel free
  488. to enhance the logic shown below, if you like, by comparing
  489. the value of OleAutoErrCode to the various E_xxxxx error code
  490. values defined in OLEAUTO.INT and varying the exception
  491. messages accordingly.
  492.  
  493. {
  494.    OLEAUTX.PAS (C) 1995 W. Raike
  495. }
  496. unit OleAutX;
  497. {*******************************************************************}
  498. {
  499.   Class TOLEAutoEx derived from TOleObject overrides virtual methods
  500.   for OLE property/method access in order to raise an exception when
  501.   such a method call is unsuccessful.
  502. }
  503. {*******************************************************************}
  504.  
  505. interface
  506.  
  507. uses
  508.   OleAuto;
  509.  
  510. type
  511.   TOleObjectEx = class(TOleObject)
  512.     procedure CallOleFunction(sMethodName : String;
  513.                         sReturnType : String; var Retval); override;
  514.     procedure CallOleProc(sMethodName : String); override;
  515.     procedure GetOleProperty(sPropertyName : String;
  516.                         sPropertyType : String; var RetVal); override;
  517.     procedure SetOleProperty(sPropertyName : String;
  518.                         sPropertyType : String; var PropVal); override;
  519.   end;
  520.  
  521. implementation
  522.  
  523. procedure TOleObjectEx.CallOleFunction(sMethodName : String;
  524.                     sReturnType : String; var Retval);
  525. begin
  526.   inherited CallOleFunction(sMethodName, sReturnType, Retval);
  527.   if OleAutoErrCode <> S_OK then
  528.     raise EOleAutoInvokeFailure.Create('OLE function call failed');
  529. end;
  530.  
  531. procedure TOleObjectEx.CallOleProc(sMethodName : String);
  532. begin
  533.   inherited CallOleProc(sMethodName);
  534.   if OleAutoErrCode <> S_OK then
  535.     raise EOleAutoInvokeFailure.Create('OLE procedure call failed');
  536. end;
  537.  
  538. procedure TOleObjectEx.GetOleProperty(sPropertyName : String;
  539.                     sPropertyType : String; var RetVal);
  540. begin
  541.   inherited GetOleProperty(sPropertyName, sPropertyType, Retval);
  542.   if OleAutoErrCode <> S_OK then
  543.     raise EOleAutoInvokeFailure.Create('OLE property get failed');
  544. end;
  545.  
  546. procedure TOleObjectEx.SetOleProperty(sPropertyName : String;
  547.                     sPropertyType : String; var PropVal);
  548. begin
  549.   inherited SetOleProperty(sPropertyName, sPropertyType, PropVal);
  550.   if OleAutoErrCode <> S_OK then
  551.     raise EOleAutoInvokeFailure.Create('OLE property set failed');
  552. end;
  553.  
  554. end.
  555.  
  556.